﻿using System;
using System.Collections.Generic;
using System.Linq;
using static LightCAD.Three.TypeUtils;
using static LightCAD.MathLib.Constants;
using LightCAD.Three.OpenGL;

namespace LightCAD.Three
{

    public class WebGLAttribute
    {
        public int type;
        public int location;
        public int locationSize;
    };
    public class BufferObject
    {
        public int buffer;
        public int type;
        public int bytesPerElement;
        public int version;
    }
    public class WebGLAttributes
    {
        public readonly bool isWebGL2;
        public readonly Dictionary<BufferAttribute, BufferObject> buffers;
        public WebGLAttributes(WebGLCapabilities capabilities)
        {
            this.isWebGL2 = capabilities.isWebGL2;
            this.buffers = new Dictionary<BufferAttribute, BufferObject>();

        }
        public BufferObject createBuffer(BufferAttribute attribute, int bufferType)
        {
            var array = attribute.arrObj;
            var usage = attribute.usage;
            var buffer = gl.createBuffer();

            gl.bindBuffer(bufferType, buffer);
            if (array is double[])
                array = toFloat32Array(attribute.array);
            if (array is float[])
                gl.bufferData(bufferType, (float[])array, usage);
            else if (array is uint[])
                gl.bufferData(bufferType, (uint[])array, usage);
            else if (array is ushort[])
            {
                gl.bufferData(bufferType, (ushort[])array, usage);
            }
            else if (array is short[])
            {
                gl.bufferData(bufferType, (short[])array, usage);
            }
            else if (array is byte[])
            {
                gl.bufferData(bufferType, (byte[])array, usage);
            }
            else if (array is sbyte[])
                gl.bufferData(bufferType, (sbyte[])array, usage);
            else
            {
                gl.bufferData(bufferType, attribute.intArray, usage);
            }
            attribute.onUploadCallback();
            int type;
            if (array is float[])
            {
                type = gl.FLOAT;
            }
            else if (array is uint[])
            {

                type = gl.UNSIGNED_INT;

            }
            else if (array is int[])
            {

                type = gl.INT;

            }
            else if (array is UInt16[])
            {

                if (attribute is Float16BufferAttribute)
                {
                    if (isWebGL2)
                    {
                        type = gl.HALF_FLOAT;
                    }
                    else
                    {
                        throw new Error("THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.");
                    }

                }
                else
                {

                    type = gl.UNSIGNED_SHORT;

                }

            }
            else if (array is short[])
            {

                type = gl.SHORT;

            }
            else if (array is byte[])
            {
                type = gl.UNSIGNED_BYTE;
            }
            else if (array is sbyte[])
            {
                type = gl.BYTE;
            }
            else
            {
                throw new Error("THREE.WebGLAttributes: Unsupported buffer data format: " + array);
            }

            return new BufferObject
            {
                buffer = buffer,
                type = type,
                bytesPerElement = array.bytesPerItem(),
                version = attribute.version
            };

        }

        public void updateBuffer(int buffer, BufferAttribute attribute, int bufferType)
        {

            var array = attribute.arrObj;
            var updateRange = attribute.updateRange;

            gl.bindBuffer(bufferType, buffer);
            if (array is double[])
            {
                array = toFloat32Array(array as double[]);
            }
            var BYTES_PER_ELEMENT = array.bytesPerItem();
            if (updateRange.count == -1)
            {
                // Not using update ranges
                gl.bufferSubData(bufferType, 0, array.byteSize(), array);
            }
            else
            {
                var subarr = array.subarray(updateRange.offset, updateRange.offset + updateRange.count);
                gl.bufferSubData(bufferType, updateRange.offset * BYTES_PER_ELEMENT, subarr.byteSize(), subarr);
                updateRange.count = -1; // reset range
            }
            attribute.onUploadCallback();
        }

        //

        public BufferObject get(BufferAttribute attribute)
        {
            if (attribute is InterleavedBufferAttribute)
                attribute = (attribute as InterleavedBufferAttribute).data;
            var data = buffers.get(attribute);
            return data;
        }

        public void remove(BufferAttribute attribute)
        {
            if (attribute is InterleavedBufferAttribute)
                attribute = (attribute as InterleavedBufferAttribute).data;
            var data = buffers.get(attribute);
            if (data != null)
            {
                gl.deleteBuffer(data.buffer);
                buffers.Remove(attribute);
            }
        }

        public void update(BufferAttribute attribute, int bufferType)
        {
            if (attribute is GLBufferAttribute)
            {
                var cached = buffers.get(attribute);
                var glBufferAttribute = attribute as GLBufferAttribute;
                if (cached == null || cached.version < attribute.version)
                {

                    buffers.set(attribute, new BufferObject
                    {
                        buffer = glBufferAttribute.buffer,
                        type = glBufferAttribute.type,
                        bytesPerElement = glBufferAttribute.elementSize,
                        version = attribute.version
                    });
                }
                return;
            }

            if (attribute is InterleavedBufferAttribute)
                attribute = (attribute as InterleavedBufferAttribute).data;
            var data = buffers.get(attribute);
            if (data == null)
            {
                buffers.set(attribute, createBuffer(attribute, bufferType));
            }
            else if (data.version < attribute.version)
            {
                updateBuffer(data.buffer, attribute, bufferType);
                data.version = attribute.version;
            }

        }
    }

}
